using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using System.Threading;

// http://xna-uk.net/blogs/randomchaos/archive/2008/08/15/generic-xna-threading-for-windows-amp-xbox-360.aspx

namespace Alone.Handlers
{
	public class ThreadedComponentData
	{
		public ThreadCodeObj threadCode;
		public ThreadStart threadStart;
		public Thread thread;

		public ThreadedComponentData(ThreadCodeObj threadCode, ThreadStart threadStart, Thread thread)
		{
			this.threadCode = threadCode;
			this.threadStart = threadStart;
			this.thread = thread;
		}
	}

	public interface IThreadManager	{ }
	
	/// <summary>
	/// This is a game component that implements IUpdateable.
	/// </summary>
	public class ThreadManager : Microsoft.Xna.Framework.GameComponent, IThreadManager
	{
		private Dictionary<int, ThreadedComponentData> components = new Dictionary<int, ThreadedComponentData>();

		/// <summary>
		/// Managers GameTime to be passed onto the threads.
		/// </summary>
		static GameTime gameTime;

		public ThreadManager(Game game)
			: base(game)
		{
			Game.Services.AddService(typeof(IThreadManager), this);
		}

		/// <summary>
		/// Allows the game component to perform any initialization it needs to before starting
		/// to run.  This is where it can query for any required services and load content.
		/// </summary>
		public override void Initialize()
		{
			base.Initialize();
		}

		/// <summary>
		/// Allows the game component to update itself.
		/// </summary>
		/// <param name="gameTime">Provides a snapshot of timing values.</param>
		public override void Update(GameTime gameTime)
		{
			ThreadManager.gameTime = gameTime;

			for (int i = 0; i < components.Count; i++)
			{
				components[i].threadCode.Update(gameTime);
			}

			base.Update(gameTime);
		}

		/// <summary>
		/// Method to add a thread to the maanger.
		/// </summary>
		/// <param name="threadCode">Code to be executed in the thread.</param>
		/// <param name="threadInterval">Time period between each call in miliseconds</param>
		/// <returns>Index of thread, first one added will be 0 next 1 etc..</returns>
		public int AddThread(ThreadCode threadCode, int threadInterval, int affinityIndex)
		{
			int retVal = components.Count;

			ThreadCodeObj thisThread = new ThreadCodeObj(threadCode, threadInterval, affinityIndex);
			ThreadStart thisStarter = new ThreadStart(thisThread.Worker);

			components.Add(components.Count, new ThreadedComponentData(thisThread, thisStarter, new Thread(thisStarter)));
			components[components.Count - 1].thread.Start();

			return retVal;
		}

		/// <summary>
		/// Method to kill a single thread.
		/// </summary>
		/// <param name="index"></param>
		public void KillThread(int index)
		{
			components[index].threadCode.KillThread(components[index].thread);
		}

		/// <summary>
		/// Method to start a thread
		/// </summary>
		/// <param name="threadCode"></param>
		/// <param name="threadInterval"></param>
		/// <param name="index"></param>
		public void StartThread(ThreadCode threadCode, int threadInterval, int index)
		{
			ThreadedComponentData component = components[index];

			if (component.threadCode.stopThread)
			{
				component.thread = new Thread(component.threadStart);
				component.threadCode.stopThread = false;
				component.thread.Start();
			}
		}

		/// <summary>
		/// Method to tidy up unfinished threads.
		/// </summary>
		/// <param name="disposing"></param>
		protected override void Dispose(bool disposing)
		{
			for (int i = 0; i < components.Count; i++)
			{
				KillThread(i);
			}

			base.Dispose(disposing);
		}
	}

	/// <summary>
	/// This is the delegate to be used for passing the code to be called in the tread.
	/// </summary>
	/// <param name="gameTime">GameTime</param>
	public delegate void ThreadCode(GameTime gameTime);

	/// <summary>
	/// This class holds the required data for the code to be called in the thread.
	/// </summary>
	internal class ThreadCodeObj
	{
		public ThreadCode CodeToCall = null;

		/// <summary>
		/// Mutex to stop thread clashes.
		/// </summary>
		private static Mutex mutex = new Mutex();
		/// <summary>
		/// Used to make the thread wait.
		/// </summary>
		private ManualResetEvent threadStopEvent = new ManualResetEvent(false);
		/// <summary>
		/// Bool to control imediate stopping of thread loop.
		/// </summary>        
		public bool stopThread = false;
		/// <summary>
		/// Interval thread will wait befoer next cycle.
		/// </summary>
		private int threadIntervals;
		private int processorAffinity;

		public GameTime gameTime;

		public ThreadCodeObj(ThreadCode code, int interval, int affinity)
		{
			CodeToCall = code;
			threadIntervals = interval;
			processorAffinity = affinity;
		}

		public void Update(GameTime gameTime)
		{
			this.gameTime = gameTime;
		}

		public void Worker()
		{
#if XBOX
            Thread.CurrentThread.SetProcessorAffinity(new int[] { processorAffinity });
#endif
			do
			{
				try
				{
					mutex.WaitOne();
					if (gameTime != null)
					{
						CodeToCall(gameTime);
					}
				}
				finally
				{
					mutex.ReleaseMutex();
				}
			}
			while (!threadStopEvent.WaitOne(threadIntervals, false) && !stopThread);
		}

		public void KillThread(Thread thread)
		{
			if (!stopThread)
			{
				mutex.WaitOne();
				stopThread = true;
				thread.Join(0);
				mutex.ReleaseMutex();
			}
		}
	}
}
